package nn.networks;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

import nn.Neuron;
import nn.layers.InputLayer;
import nn.layers.Layer;


public class Network {

	protected ArrayList<Layer> layerList = new ArrayList<Layer>();

	public static void main(String[] args) throws Exception {
		boolean printNetwork = false;
		Network net = new Network();
		String path;
		path = "configurations/AND";
		net.readConfFile(path);
		printNetwork(net);
		
		float[] input1 = { 0, 0 };
		float[] input2 = { 0, 1 };
		float[] input3 = { 1, 0 };
		float[] input4 = { 1, 1 };
		float[] result;
		ArrayList<float[]> list = new ArrayList<float[]>();
		list.add(input1);
		list.add(input2);
		list.add(input3);
		list.add(input4);
		System.out.println("result:");
		String sRes;
		for (float[] it : list) {
			sRes = "";
			result = net.calculate(it);
			for (int i = 0; i < result.length; ++i) {
				sRes += ", " + result[i];
			}
			System.out.println("(" + (int) it[0] + "," + (int) it[1] + ") ->"
					+ sRes);
		}
		if (printNetwork) {
			Network.printNetwork(net);
		}
	}

	public static void printNetwork(Network net) {
		int count = 0;
		System.out.println("layer count: "+net.layerList.size());
		for (Layer l : net.layerList) {
			System.out.println("layer: " + count + ", size: " + +l.size);
			if (l instanceof InputLayer) {
				System.out.println("\tinput layer");
			} else {
				for (Neuron n : l.getNeuronList()) {

					float[] w = n.getWeights();
					String s = "";
					for (int i = 0; i < w.length; ++i) {
						s = s + ", " + w[i];
					}
					System.out.println("\t" + s.substring(2) + ", bias:"
							+ n.getBias());
				}
			}
			++count;
		}
	}

	public float[] calculate(float[] input) throws Exception {
		if (input.length != layerList.get(0).size) {
			throw new Exception("input size not match network input, "
					+ input.length + " instead of " + layerList.get(0).size);
		}
		float[] result = input;
		for (int it = 1; it < layerList.size(); ++it) {
			result = layerList.get(it).getResults(result);
		}
		return result;
	}

	/**
	 * reads layers configuration from file. first n lines represents number of
	 * layers neurons. Each line should contains one number with number of its neurons.
	 * Optionally line can contain type of this layers activation function.
	 * Strings with act. func. names are specified in ActivationFunctionFactory
	 * class.
	 * 
	 * After layers specification file must contain line with key word "END".
	 * Every next line contain weights of each layer (without input layer)
	 * neurons. E.g. if network contains input layer and 2 other layers, first
	 * with 2 neurons and second with 4 neurons, after line with keyword "END"
	 * file must contains 6 lines. Two with <input_size> float weights, and four
	 * with two float weights. Optionally, bias for this neuron can be specified
	 * as additional number at the end of line.
	 * 
	 * @param path
	 *            path to file
	 * @throws IOException
	 *             Signals that an I/O exception has occurred or file don't have
	 *             proper format.
	 */
	// TODO - min 1 layer
	public void readConfFile(String path) throws IOException {
		DataInputStream in;
		FileInputStream fstream;
		Layer layer;
		int lineCount = 1;
		try {
			fstream = new FileInputStream(path);
			in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String line;
			line = br.readLine();
			// reading layers parameters (size, bias activation function)
			while (line != null) {
				if (line.startsWith("#")) {
					line = br.readLine();
					++lineCount;
					continue;
				}
				if (line.equals("")) {
					line = br.readLine();
					++lineCount;
					continue;
				}
				if (line.contains("END")) {
					break;
				}
				String[] lineArray = line.split("\\s+");
				switch (lineArray.length) {
				case 1:
					layer = new Layer(Integer.parseInt(lineArray[0]));
					break;
				case 2:
					layer = new Layer(Integer.parseInt(lineArray[0]),
							lineArray[1]);
					break;
				case 0:
					// TODO - chceck if ok (continue)
					line = br.readLine();
					++lineCount;
					continue;
				default:
					throw new IOException(
							"illegal arguments or to many arguments in line:"
									+ lineCount);
				}
				layerList.add(layer);
				line = br.readLine();
				++lineCount;
			}

			if (line == null) {
				throw new IOException("no \"END\" line");
			}
			Layer firstLayer = layerList.remove(0);
			layerList.add(0, new InputLayer(firstLayer));

			line = br.readLine();
			++lineCount;
			int layerCounter = 1;
			// reading neurons weights
			while (line != null) {
				if (line.startsWith("#")) {
					line = br.readLine();
					++lineCount;
					continue;
				}
				if (layerCounter > layerList.size()) {
					throw new IOException("to many weights lines, instead of "
							+ layerList.size());
				}

				if (line.split("\\s+").length == 0 || line.equals("")) {
					line = br.readLine();
					++lineCount;
					continue;
				}
				int layerSize = layerList.get(layerCounter).size;
				int prevLayerSize = layerList.get(layerCounter - 1).size;
				// one layer's neurons
				for (int neuronIt = 0; neuronIt < layerSize; ++neuronIt) {
					String[] lineArray = line.split("\\s+");
					if (prevLayerSize != lineArray.length
							&& (prevLayerSize + 1) != lineArray.length) {
						throw new IOException("number of weights ("+lineArray.length+")in line "
								+ lineCount + " do not correspond to layer "
								+ (layerCounter - 1) + " size ("+prevLayerSize+"), layer "
								+ layerCounter + " neurons should have "
								+ prevLayerSize + " weights");
					}
					float[] weightsArray = new float[prevLayerSize];
					for (int it = 0; it < prevLayerSize; ++it) {
						weightsArray[it] = Float.parseFloat(lineArray[it]);
					}
					System.out.println(layerSize + " " + weightsArray.length);
					layerList.get(layerCounter).setNeuronWeights(neuronIt,
							weightsArray);
					if ((prevLayerSize + 1) == lineArray.length) {
						layerList.get(layerCounter).setNeuronBias(neuronIt,
								Float.parseFloat(lineArray[prevLayerSize]));
					}
					++lineCount;
					line = br.readLine();
				}

				++layerCounter;
			}
			if (layerCounter != layerList.size()) {
				throw new IOException("to few weights lines " + layerCounter
						+ " instead of " + layerList.size());
			}

			in.close();
		} catch (IOException e) {
			// System.err.println("Error while reading file");
			throw e;
		}
	}

	public void readConfFile2(String path) throws IOException {
		DataInputStream in;
		FileInputStream fstream;
		Layer layer;
		int lineCount = 1;
		try {
			fstream = new FileInputStream(path);
			in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String line;
			line = br.readLine();
			// reading layers parameters (size, bias activation function)
			while (line != null) {
				if (line.equals("")) {
					line = br.readLine();
					++lineCount;
					continue;
				}
				if (line.contains("END")) {
					break;
				}
				String[] lineArray = line.split("\\s+");
				switch (lineArray.length) {
				case 1:
					layer = new Layer(Integer.parseInt(lineArray[0]));
					break;
				case 2:
					layer = new Layer(Integer.parseInt(lineArray[0]),
							lineArray[1]);
					break;
				case 0:
					// TODO - chceck if ok (continue)
					line = br.readLine();
					++lineCount;
					continue;
				default:
					throw new IOException(
							"illegal arguments or to many arguments in line:"
									+ lineCount);
				}
				layerList.add(layer);
				line = br.readLine();
				++lineCount;
			}

			if (line == null) {
				throw new IOException("no \"END\" line");
			}
			Layer firstLayer = layerList.remove(0);
			layerList.add(0, new InputLayer(firstLayer));

			line = br.readLine();
			++lineCount;
			int layerCounter = 1;

			// reading neurons weights
			while (line != null) {
				if (layerCounter > layerList.size()) {
					throw new IOException("to many weights lines, instead of "
							+ layerList.size());
				}

				if (line.split("\\s+").length == 0 || line.equals("")) {
					line = br.readLine();
					++lineCount;
					continue;

				}
				int layerSize = layerList.get(layerCounter).size;
				int prevLayerSize = layerList.get(layerCounter - 1).size;

				if (line.split("\\s+").length == 1) {

					for (int neuronIt = 0; neuronIt < layerSize; ++neuronIt) {

						String[] lineArray = line.split("\\s+");
						float[] weights = new float[prevLayerSize];

						for (int it = 0; it < prevLayerSize; ++it) {
							weights[it] = 0;
						}

						layerList.get(layerCounter).setNeuronWeights(neuronIt,
								weights);
						layerList.get(layerCounter).setNeuronBias(neuronIt,
								Float.parseFloat(lineArray[0]));
						line = br.readLine();
						++lineCount;
					}

				} else if (line.split("\\s+").length == 3) {

					for (int neuronIt = 0; neuronIt < layerSize; ++neuronIt) {

						String[] lineArray = line.split("\\s+");
						float[] weights = new float[prevLayerSize];
						float[] args = new float[2];
						for (int it = 1; it < lineArray.length; it++) {
							args[it - 1] = Float.parseFloat(lineArray[it]);
						}
						float min, max;

						if (args[0] > args[1]) {
							max = args[0];
							min = args[1];
						} else {
							max = args[1];
							min = args[0];
						}

						for (int it = 0; it < prevLayerSize; ++it) {
							weights[it] = (float) (min + (Math.random() * (max - min)));

						}

						layerList.get(layerCounter).setNeuronWeights(neuronIt,
								weights);
						layerList.get(layerCounter).setNeuronBias(neuronIt,
								Float.parseFloat(lineArray[0]));
						line = br.readLine();
						++lineCount;
					}

				} else {
					throw new IOException("too many arguments in line");
				}
				++layerCounter;
			}
			if (layerCounter != layerList.size()) {
				throw new IOException("to few weights lines " + layerCounter
						+ " instead of " + layerList.size());
			}

			in.close();
		} catch (IOException e) {
			// System.err.println("Error while reading file");
			throw e;
		}
	}
}
